﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;

namespace Common.DLR
{
    ///<summary>
    ///</summary>
    public class CodeEvaluation
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="className"></param>
        /// <param name="externalReferences"></param>
        /// <param name="parameters"></param>
        /// <param name="codeBlock"></param>
        /// <returns></returns>
        public static object CreateClassInMemory(string className, string[] externalReferences, Dictionary<string, object> parameters, string codeBlock)
        {
            CompilerParameters compilerParms = new CompilerParameters();
            compilerParms.ReferencedAssemblies.Add("System.dll");
            compilerParms.ReferencedAssemblies.Add("System.Xml.dll");
            compilerParms.ReferencedAssemblies.Add("System.Data.dll");
            if(externalReferences !=null && externalReferences.Length>0)
            {
                foreach(string externalRef in externalReferences)
                {
                    if(!compilerParms.ReferencedAssemblies.Contains(externalRef))
                    {
                        compilerParms.ReferencedAssemblies.Add(externalRef);
                    }
                }
            }
            compilerParms.GenerateInMemory = true;

            StringBuilder block = new StringBuilder();
            block.Append("using System;\n");
            block.Append("using System.Collections.Generic;\n");
            block.Append("using System.ComponentModel;\n");
            block.Append("using System.IO;\n");
            block.Append("using System.Text;\n");
            block.Append("using System.Data;\n");
            // block.Append("using System.Drawing;\n");
            block.Append("using System.Xml;\n\n");
            block.Append("using System.Text.RegularExpressions;\n");
            //block.Append("using System.Windows.Forms;\n");

            block.Append("namespace MyNamespace {\n");
            block.Append("public class " + className + " {\n");

            // fields and props
            if (parameters != null && parameters.Count > 0)
            {
                foreach (string keyName in parameters.Keys)
                {
                    Type fieldType = parameters[keyName].GetType();
                    // field declaration 
                    block.Append("private " + fieldType.ToString() + " _" + keyName + ";\n");
                    // get method
                    block.Append("public " + fieldType.ToString() + " Get_" + keyName + "(){\n");
                    block.Append("return _" + keyName + ";\n");
                    block.Append("}\n");
                    // set method
                    block.Append("public void Set_" + keyName + "(" + fieldType.ToString() + " value){\n");
                    block.Append("this._" + keyName + "=value;\n");
                    block.Append("}\n");
                }
            }

            // method
            block.Append("public void Execute(){\n");
            block.Append(codeBlock);
            block.Append("} \n");
            block.Append("} \n");
            block.Append("} \n");

            CSharpCodeProvider codeProvider = new CSharpCodeProvider();
            CompilerResults compileResult = codeProvider.CompileAssemblyFromSource(compilerParms, block.ToString());
            if (compileResult.Errors.HasErrors)
            {
                throw new Exception(compileResult.Errors[0].ErrorNumber + ": " + compileResult.Errors[0].ErrorText);
            }
            Assembly localAssem = compileResult.CompiledAssembly;
            object instance = localAssem.CreateInstance("MyNamespace." + className);
            return instance;
        }

        ///<summary>
        ///</summary>
        ///<param name="parameters"></param>
        ///<param name="instance"></param>
        ///<returns></returns>
        public static bool ExecuteCodeBlock(ref Dictionary<string, object> parameters, object instance)
        {
            bool succeed = false;
            try
            {
                if (parameters != null && parameters.Count > 0)
                {
                    foreach (string valName in parameters.Keys)
                    {
                        object val = parameters[valName];
                        instance.GetType().InvokeMember("Set_" + valName, BindingFlags.InvokeMethod, null, instance,
                                                        new object[] { val });
                    }
                }

                instance.GetType().InvokeMember("Execute", BindingFlags.InvokeMethod, null, instance, null);
                succeed = true;

                if (parameters != null && parameters.Count > 0)
                {
                    string[] keys = new string[parameters.Count];
                    parameters.Keys.CopyTo(keys, 0);

                    for (int i = 0; i < keys.Length; i++)
                    {
                        string key = keys[i];
                        object evaluatedParameter =
                            instance.GetType().InvokeMember("Get_" + key, BindingFlags.InvokeMethod, null, instance,
                                                            null);
                        parameters[key] = evaluatedParameter;
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionHandler.HandlesException(ex);
                succeed = false;
            }
            return succeed;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="className"></param>
        /// <param name="externalReferences"></param>
        /// <param name="parameters"></param>
        /// <param name="codeBlock"></param>
        /// <returns></returns>
        public static bool ExecuteCodeBlock(string className, string[] externalReferences, ref Dictionary<string, object> parameters, string codeBlock)
        {
            bool succeed = false;
            try
            {
                CompilerParameters compilerParms = new CompilerParameters();
                compilerParms.ReferencedAssemblies.Add("System.dll");
                compilerParms.ReferencedAssemblies.Add("System.Xml.dll");
                compilerParms.ReferencedAssemblies.Add("System.Data.dll");
                if (externalReferences != null && externalReferences.Length > 0)
                {
                    foreach (string externalRef in externalReferences)
                    {
                        if (!compilerParms.ReferencedAssemblies.Contains(externalRef))
                        {
                            compilerParms.ReferencedAssemblies.Add(externalRef);
                        }
                    }
                }
                compilerParms.GenerateInMemory = true;

                StringBuilder block = new StringBuilder();
                block.Append("using System;\n");
                block.Append("using System.Collections.Generic;\n");
                block.Append("using System.ComponentModel;\n");
                block.Append("using System.IO;\n");
                block.Append("using System.Text;\n");
                block.Append("using System.Data;\n");
                // block.Append("using System.Drawing;\n");
                block.Append("using System.Xml;\n\n");
                block.Append("using System.Text.RegularExpressions;\n");
                //block.Append("using System.Windows.Forms;\n");

                block.Append("namespace MyNamespace {\n");
                block.Append("public class " + className + " {\n");

                // fields and props
                if (parameters != null && parameters.Count > 0)
                {
                    foreach (string keyName in parameters.Keys)
                    {
                        Type fieldType = parameters[keyName].GetType();
                        // field declaration 
                        block.Append("private " + fieldType.ToString() + " _" + keyName + ";\n");
                        // get method
                        block.Append("public " + fieldType.ToString() + " Get_" + keyName + "(){\n");
                        block.Append("return _" + keyName + ";\n");
                        block.Append("}\n");
                        // set method
                        block.Append("public void Set_" + keyName + "(" + fieldType.ToString() + " value){\n");
                        block.Append("this._" + keyName + "=value;\n");
                        block.Append("}\n");
                    }
                }

                // method
                block.Append("public void Execute(){\n");
                block.Append(codeBlock);
                block.Append("} \n");
                block.Append("} \n");
                block.Append("} \n");

                CSharpCodeProvider codeProvider = new CSharpCodeProvider();
                CompilerResults compileResult = codeProvider.CompileAssemblyFromSource(compilerParms, block.ToString());
                if (compileResult.Errors.HasErrors)
                {
                    throw new Exception(compileResult.Errors[0].ErrorNumber + ": " + compileResult.Errors[0].ErrorText);
                }
                Assembly localAssem = compileResult.CompiledAssembly;
                object instance = localAssem.CreateInstance("MyNamespace." + className);

                // initialize values 
                if (parameters != null && parameters.Count > 0)
                {
                    foreach (string valName in parameters.Keys)
                    {
                        object val = parameters[valName];
                        instance.GetType().InvokeMember("Set_" + valName, BindingFlags.InvokeMethod, null, instance, new object[] { val });
                    }
                }

                instance.GetType().InvokeMember("Execute", BindingFlags.InvokeMethod, null, instance, null);
                succeed = true;

                if (parameters != null && parameters.Count > 0)
                {
                    string[] keys = new string[parameters.Count];
                    parameters.Keys.CopyTo(keys, 0);

                    for (int i = 0; i < keys.Length; i++)
                    {
                        string key = keys[i];
                        object evaluatedParameter =
                            instance.GetType().InvokeMember("Get_" + key, BindingFlags.InvokeMethod, null, instance,
                                                            null);
                        parameters[key] = evaluatedParameter;
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionHandler.HandlesException(ex);
                succeed = false;
            }
            return succeed;
        }
    }
}
